
// Copyright (C) 2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Xml;

namespace Derm.Xml.Collada
{
	/// <summary>
	/// The technique_common element specifies the bind_material information for the common 
	/// profile which all COLLADA implementations need to support.
	/// </summary>
	public class ColladaBindMaterialCommonTechnique : ColladaXmlSerializable
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaBindMaterialCommonTechnique.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaBindMaterialCommonTechnique.
		/// </param>
		public ColladaBindMaterialCommonTechnique(ColladaXmlSerializable parent) : base(parent)
		{

		}

		#endregion

		#region Bind Material Common Technique Information

		/// <summary>
		/// The instance_material element specifies the information needed to bind a geometry
		/// to a material. This element must appear at least once.
		/// </summary>
		public readonly List<ColladaInstanceMaterial> InstanceMaterials = new List<ColladaInstanceMaterial>();

		#endregion

		#region ColladaXmlSerializable Overrides

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is responsible to read the start element of the nested element.
		/// </para>
		/// <para>
		/// This method is resposible to read all children elements till the corresponding end element, without reading it. In the
		/// case this condition is not satisfied, the XML parsing will be aborted.
		/// </para>
		/// </remarks>
		public override void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			if (elementName == ColladaElementInstanceMaterial) {
				ColladaInstanceMaterial colladaInstanceMaterial = new ColladaInstanceMaterial(this);
				colladaInstanceMaterial.ReadXml(xmlReader);
				InstanceMaterials.Add(colladaInstanceMaterial);
			} else
				base.ReadElement(elementName, xmlReader);
		}

		/// <summary>
		/// Collada 'bind_material/technique_common' child element name (1.4).
		/// </summary>
		private const string ColladaElementInstanceMaterial = "instance_material";

		#endregion
	}

	/// <summary>
	/// Bind a specific material to a piece of geometry, binding varying and uniform parameters at the same time.
	/// </summary>
	public class ColladaBindMaterial : ColladaXmlSerializable
	{
		#region Constructors

		/// <summary>
		/// Construct a ColladaBindMaterial.
		/// </summary>
		/// <param name="parent">
		/// The parent element containing this ColladaBindMaterial.
		/// </param>
		public ColladaBindMaterial(ColladaXmlSerializable parent) : base(parent)
		{

		}

		#endregion

		#region Bind Material Information

		/// <summary>
		/// The bind_material element may contain any number of param elements.
		/// </summary>
		public readonly List<ColladaParam> Params = new List<ColladaParam>();

		/// <summary>
		/// The technique_common element specifies the bind_material information for the common 
		/// profile which all COLLADA implementations need to support.
		/// </summary>
		public ColladaBindMaterialCommonTechnique CommonTechnique;

		/// <summary>
		/// This element may contain any number of non-common profile techniques.
		/// </summary>
		public readonly List<ColladaTechnique> Techniques = new List<ColladaTechnique>();

		/// <summary>
		///The extra element may appear any number of times.
		/// </summary>
		public readonly List<ColladaExtra> Extras = new List<ColladaExtra>();

		#endregion

		#region COLLADA Support

		/// <summary>
		/// Check whether this element defines a material.
		/// </summary>
		/// <param name="material">
		/// A <see cref="System.String"/> that specify the material name.
		/// </param>
		/// <returns></returns>
		internal bool HasMaterial(string material)
		{
			// Material defined using common technique?
			if (CommonTechnique != null) {
				bool found = CommonTechnique.InstanceMaterials.Exists(delegate(ColladaInstanceMaterial instanceMaterial)
              	{
					return (instanceMaterial.Symbol == material);
              	});

				if (found == true)
					return (true);
			}

			return (false);
		}

		/// <summary>
		/// Create a <see cref="ColladaShaderParameters"/> from this <see cref="ColladaBindMaterial"/>.
		/// </summary>
		internal ColladaShaderParameters Create(string material)
		{
			if (CommonTechnique == null)
				throw new InvalidOperationException("no common technique");

			foreach (ColladaInstanceMaterial instanceMaterial in CommonTechnique.InstanceMaterials) {
				if (instanceMaterial.Symbol == material)
					return (instanceMaterial.Instance.Create());
			}

			throw new InvalidOperationException(String.Format("unable to bind material {0}", material));
		}

		#endregion

		#region ColladaXmlSerializable Overrides

		/// <summary>
		/// Read an element of the current XML serializable.
		/// </summary>
		/// <param name="elementName">
		/// A <see cref="System.String"/> that specify the element name.
		/// </param>
		/// <param name="xmlReader">
		/// A <see cref="XmlReader"/> used for reading child element. It is positioned at the start element.
		/// </param>
		/// <remarks>
		/// <para>
		/// This method is responsible to read the start element of the nested element.
		/// </para>
		/// <para>
		/// This method is resposible to read all children elements till the corresponding end element, without reading it. In the
		/// case this condition is not satisfied, the XML parsing will be aborted.
		/// </para>
		/// </remarks>
		public override void ReadElement(string elementName, XmlReader xmlReader)
		{
			if (elementName == null)
				throw new ArgumentNullException("elementName");
			if (xmlReader == null)
				throw new ArgumentNullException("xmlReader");

			if (elementName == ColladaElementParam) {
				ColladaParam colladaParam = new ColladaParam(this);
				colladaParam.ReadXml(xmlReader);
				Params.Add(colladaParam);
			} else if (elementName == ColladaElementCommonTechnique) {
				CommonTechnique = new ColladaBindMaterialCommonTechnique(this);
				CommonTechnique.ReadXml(xmlReader);
			} else if (elementName == ColladaElementTechnique) {
				Techniques.Add(ColladaTechnique.ReadTechique(xmlReader, this));
			} else if (elementName == ColladaElementExtra) {
				ColladaExtra colladaExtra = new ColladaExtra(this);
				colladaExtra.ReadXml(xmlReader);
				Extras.Add(colladaExtra);
			} else
				base.ReadElement(elementName, xmlReader);
		}

		/// <summary>
		/// Collada 'bind_material' child element name (1.4).
		/// </summary>
		private const string ColladaElementParam = "param";

		/// <summary>
		/// Collada 'bind_material' child element name (1.4).
		/// </summary>
		private const string ColladaElementCommonTechnique = "technique_common";

		/// <summary>
		/// Collada 'bind_material' child element name (1.4).
		/// </summary>
		private const string ColladaElementTechnique = "technique";

		/// <summary>
		/// Collada 'bind_material' child element name (1.4).
		/// </summary>
		private const string ColladaElementExtra = "extra";

		#endregion
	}
}
